/*
<license>
Copyright (c) 2008, Chris Venter <chris.venter@gmail.com>
All rights reserved.

This file is part of quicktag <http://code.google.com/p/quicktag/>

quicktag is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.

quicktag is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this code.
If not, see <http://www.gnu.org/licenses/>.
</license>
*/
#include "id3_tag.h"

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>

#include <id3tag.h>

#include <iostream>

gchar* FILE_READING_ID3V1V2_CHARACTER_SET		= g_strdup("UTF-8");
gchar* FILE_WRITING_ID3V2_NO_UNICODE_CHARACTER_SET 	= g_strdup("ISO-8859-1");

// what do these do?
enum {
    EASYTAG_ID3_FIELD_LATIN1        = 0x0001,
    EASYTAG_ID3_FIELD_LATIN1FULL    = 0x0002,
    EASYTAG_ID3_FIELD_LATIN1LIST    = 0x0004,
    EASYTAG_ID3_FIELD_STRING        = 0x0008,
    EASYTAG_ID3_FIELD_STRINGFULL    = 0x0010,
    EASYTAG_ID3_FIELD_STRINGLIST    = 0x0020,
    EASYTAG_ID3_FIELD_LANGUAGE      = 0x0040
};

gchar* convert_string(const gchar *string, const gchar *from_codeset, const gchar *to_codeset, const gboolean display_error)
{
	gchar* new_string = (gchar*)malloc(strlen(string) * sizeof(gchar));
	strcpy(new_string, string);
	return new_string;
}

static int    etag_ucs42gchar           (const id3_ucs4_t *usrc, unsigned is_latin, unsigned is_utf16, gchar **res);
static int    libid3tag_Get_Frame_Str   (const struct id3_frame *frame, unsigned etag_field_type, gchar **retstr);

namespace quicktag
{

	//gboolean read_id3(const gchar *filename, File_Tag *FileTag)
	int read_id3(const std::string& filename, quicktag::tags& tags)
	{
		//std::cout<<"opening "<<filename<<std::endl;
		// open the file
		int tmpfile;
		if ( (tmpfile = open(filename.c_str(), O_RDONLY)) < 0 )
		{
			tags.seterror("could not open file [" + filename + "]");
			return READVAL_FILE;
		}
		
		// Check if the file has an ID3v2 tag or/and an ID3v1 tags
	    
		// 1) ID3v2 tag
		bool id3v2 = false;
		bool id3v1 = false;
		
		// read ID3_TAG_QUERYSIZE bytes into a character array:
		char querysizebuf[ID3_TAG_QUERYSIZE];
		if (read(tmpfile, querysizebuf, ID3_TAG_QUERYSIZE) != ID3_TAG_QUERYSIZE)
		{
			close(tmpfile);
			return READVAL_ACCESS;
		}
		int tagsize = id3_tag_query((id3_byte_t const *)querysizebuf, ID3_TAG_QUERYSIZE);
		if (tagsize > ID3_TAG_QUERYSIZE)
		{
			//std::cout<<"potential ID3V2 tag found"<<std::endl;
			id3v2 = true;
		}

		// 2) ID3v1 tag
		if (lseek(tmpfile,-128, SEEK_END) >= 0) // Go to the beginning of ID3v1 tag
		{
			char tagbuf[3];
			if (read(tmpfile, tagbuf, 3) != 3)
			{
				close(tmpfile);
				return READVAL_ACCESS;
			}
			if (!strncmp(tagbuf, "TAG", 3))
			{
				//std::cout<<"potential ID3V1 tag found"<<std::endl;
				id3v1 = true;
			}
		}
		else
		{
			close(tmpfile);
			return READVAL_ACCESS;
		}
		
		// process the file using the id3tag library
		
		id3_file* file = id3_file_fdopen(tmpfile, ID3_FILE_MODE_READONLY);
		if (file == 0)
		{
			tags.seterror("id3_file_fdopen: could not open file [" + filename + "] readonly");
			close(tmpfile);
			return READVAL_FILE;
		}
		
		id3_tag* tag = id3_file_tag(file);
		if (tag == 0 || tag->nframes == 0)
		{
			id3_file_close(file);
			return READVAL_FAIL;
		}

		// Title (TIT2)
		id3_frame *frame = id3_tag_findframe(tag, ID3_FRAME_TITLE, 0);
		if (frame)
		{
			char* title;
			libid3tag_Get_Frame_Str(frame, EASYTAG_ID3_FIELD_STRINGLIST, &title);
			if (title)
			{
				tags.title = title;
				free(title);
			}
		}
		
		id3_field *field;

		// Artist (TPE1)
		if ( (frame = id3_tag_findframe(tag, ID3_FRAME_ARTIST, 0)) )
		{
			char* artist;
			libid3tag_Get_Frame_Str(frame, EASYTAG_ID3_FIELD_STRINGLIST, &artist);
			if (artist)
			{
				tags.artist = artist;
				free(artist);
			}
		}

		// Album (TALB)
		if ( (frame = id3_tag_findframe(tag, ID3_FRAME_ALBUM, 0)) )
		{
			char* album;
			libid3tag_Get_Frame_Str(frame, ~0, &album);
			if (album)
			{
				tags.album = album;
				free(album);
			}
		}

		/*
		// Part of a set (TPOS)
		if ( (frame = id3_tag_findframe(tag, "TPOS", 0)) )
		{
			libid3tag_Get_Frame_Str(frame, ~0, &FileTag->disc_number);
		}
		*/

		// Year (TYER/TDRC)
		if ( (frame = id3_tag_findframe(tag, ID3_FRAME_YEAR, 0)) )
		{
			char* year = 0;
			libid3tag_Get_Frame_Str(frame, ~0, &year);
			if (year)
			{
				tags.year = year;
				free(year);
			}
		}

		// Track and Total Track (TRCK)
		if ( (frame = id3_tag_findframe(tag, ID3_FRAME_TRACK, 0)) )
		{
			char* start_of_track = 0;
			libid3tag_Get_Frame_Str(frame, ~0, &start_of_track);

			// track and total_track will(should) be in the format "<track>/<total_track>"
			// so we split it up, and read <track> into FileTag->track and <total_track> into FileTag->track_total
			// if the seperator "/" is not found, we read the whole string into FileTag->track
			if (start_of_track)
			{
				char* start_of_total_track = g_utf8_strchr(start_of_track, -1, '/');
				{
					if (start_of_total_track)
					{
						tags.ntracks = start_of_total_track + 1;
						*start_of_total_track = '\0'; // To cut off the start_of_local_track part
					}
					tags.tracknr = start_of_track;
				}
				free(start_of_track);
			}
		}

		// Genre (TCON)
		if ( (frame = id3_tag_findframe(tag, ID3_FRAME_GENRE, 0)) )
		{
			char* genre;
			libid3tag_Get_Frame_Str(frame, ~0, &genre);
			if (genre)
			{
				tags.genre = genre;
				free(genre);
			}
		}

		// Comment (COMM)
		if ( (frame = id3_tag_findframe(tag, ID3_FRAME_COMMENT, 0)) )
		{
			char* comment;
			libid3tag_Get_Frame_Str(frame, EASYTAG_ID3_FIELD_STRINGFULL, &comment);
			if (comment)
			{
				tags.comment = comment;
				free(comment);
			}
		}

/*	    
	    // Composer (TCOM) 
	    if ( (frame = id3_tag_findframe(tag, "TCOM", 0)) )
		libid3tag_Get_Frame_Str(frame, ~0, &FileTag->composer);

	    // Original artist (TOPE) 
	    if ( (frame = id3_tag_findframe(tag, "TOPE", 0)) )
		libid3tag_Get_Frame_Str(frame, ~0, &FileTag->orig_artist);

	    
	    // Copyright (TCOP)
	    if ( (frame = id3_tag_findframe(tag, "TCOP", 0)) )
		libid3tag_Get_Frame_Str(frame, ~0, &FileTag->copyright);

	    // URL (WXXX) 
	    if ( (frame = id3_tag_findframe(tag, "WXXX", 0)) )
		libid3tag_Get_Frame_Str(frame, EASYTAG_ID3_FIELD_LATIN1, &FileTag->url);

	    // Encoded by (TENC) or (TXXX) 
	    if ( (frame = id3_tag_findframe(tag, "TENC", 0)) )
		libid3tag_Get_Frame_Str(frame, ~0, &FileTag->encoded_by);
	    
	    // check wether we got anything in the TENC tag
	    if (!FileTag->encoded_by)
	    {
	    	// if not, try the TXXX frames
	    	// try one after the other, and look for a "encoded by - "... string
	    	// if this is found, copy everything after the "encoded by - " into the encoded_by tag
		    // Encoded by in TXXX frames 
		    for (i = 0; (frame = id3_tag_findframe(tag, "TXX", i)); i++)
		    {
			// Do nothing if already read...
		//        if (FileTag->encoded_by)
		//            break;
		
			gchar* encodedby = 0;
			libid3tag_Get_Frame_Str(frame, ~0, &encodedby);
			if (encodedby)
			{
			    if (strncasecmp(encodedby, EASYTAG_STRING_ENCODEDBY MULTIFIELD_SEPARATOR, strlen(EASYTAG_STRING_ENCODEDBY MULTIFIELD_SEPARATOR)) == 0)
			    {
				FileTag->encoded_by = g_strdup(&encodedby[sizeof(EASYTAG_STRING_ENCODEDBY) + sizeof(MULTIFIELD_SEPARATOR) - 2]);
				g_free(string1);
			    }else
				g_free(string1);
			}
		    }
	    }

	    // Picture (APIC)
	    for (i = 0; (frame = id3_tag_findframe(tag, "APIC", i)); i++)
	    {
		Picture *pic;

		pic = Picture_Allocate();
		if (!prev_pic)
		    FileTag->picture = pic;
		else
		    prev_pic->next = pic;
		prev_pic = pic;

		pic->data = NULL;

		// Picture file data
		for (j = 0; (field = id3_frame_field(frame, j)); j++)
		{
		    switch (id3_field_type(field))
		    {
		        case ID3_FIELD_TYPE_BINARYDATA:
		            {
		                id3_length_t size;
		                id3_byte_t const *data;
		                
		                data = id3_field_getbinarydata(field, &size);
		                if (pic->data)
		                    g_free(pic->data);
		                if ( data && (pic->data = (guchar*)g_memdup(data, size)) )
		                    pic->size = size;
		            }
		            break;
		        case ID3_FIELD_TYPE_INT8:
		            pic->type = id3_field_getint(field);
		            break;
		        default:
		            break;
		    }
		}

		// Picture description
		libid3tag_Get_Frame_Str(frame, EASYTAG_ID3_FIELD_STRING, &pic->description);
	    }
*/
	     // Lyrics (SYLC/USLT)
	    /** see also id3/misc_support.h  **
	    if ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_SYNCEDLYRICS)) )
	    {
		gulong  size = 0;
		guchar *data = NULL;
		gchar  *description = NULL;
		gchar  *language = NULL;
		gint timestamp_format = 0;
		gint sync_type = 0;

		// SyncLyrics data
		if ( (id3_field = ID3Frame_GetField(id3_frame, ID3FN_DATA)) )
		{
		    size = ID3Field_Size(id3_field);
		    data = g_malloc(size);
		    ID3Field_GetBINARY(id3_field, data, size);
		}

		// SyncLyrics description
		description = Id3tag_Get_Field(id3_frame, ID3FN_DESCRIPTION);

		// SyncLyrics language
		language = Id3tag_Get_Field(id3_frame, ID3FN_LANGUAGE);

		// SyncLyrics timestamp field
		if ( (id3_field = ID3Frame_GetField(id3_frame, ID3FN_TIMESTAMPFORMAT)) )
		{
		    timestamp_format = ID3Field_GetINT(id3_field);
		}

		// SyncLyrics content type
		if ( (id3_field = ID3Frame_GetField(id3_frame, ID3FN_CONTENTTYPE)) )
		{
		    sync_type = ID3Field_GetINT(id3_field);
		}

		// Print data
		// j.a. Pouwelse - pouwelse :
		//      http://sourceforge.net/tracker/index.php?func=detail&aid=401873&group_id=979&atid=300979
		{
		    char tag[255];
		    unsigned int time;
		    luint pos = 0;

		    g_print("SyncLyrics/description      : %s\n",description);
		    g_print("SyncLyrics/language         : %s\n",language);
		    g_print("SyncLyrics/timestamp format : %s (%d)\n",timestamp_format==ID3TSF_FRAME ? "ID3TSF_FRAME" : timestamp_format==ID3TSF_MS ? "ID3TSF_MS" : "" ,timestamp_format);
		    g_print("SyncLyrics/sync type        : %s (%d)\n",sync_type==ID3CT_LYRICS ? "ID3CT_LYRICS" : "",sync_type);


		    g_print("SyncLyrics size             : %d\n", size);
		    g_print("Lyrics/data :\n");
		    while (pos < size)
		    {
		        strcpy(tag,data+pos);
		        //g_print("txt start=%d ",pos);
		        pos+=strlen(tag)+1;             // shift string and terminating \0
		        //g_print("txt end=%d ",pos);
		        memcpy(&time,data+pos,4);
		        pos+=4;
		        //g_print("%d -> %s\n",time,tag);
		        g_print("%s",tag);
		    }
		}

	    } **/

	    id3_file_close(file);

	    return READVAL_OK;
	}
	
}

static int
libid3tag_Get_Frame_Str(const struct id3_frame *frame, unsigned etag_field_type, gchar **retstr)
{
    const union id3_field  *field;
    unsigned    i, j, strcnt;
    gchar   *ret, *tmpstr, *tmpstr2, *latinstr;
    unsigned field_type;
    const id3_ucs4_t *usrc;
    unsigned is_latin, is_utf16;
    unsigned retval;

    ret = NULL;
    retval = 0;
    is_latin = 1, is_utf16 = 0;

    // Find the encoding used for the field
    for (i = 0; (field = id3_frame_field(frame, i)); i++)
    {
        if (id3_field_type(field) == ID3_FIELD_TYPE_TEXTENCODING)
        {
            is_latin = (id3_field_gettextencoding(field) == ID3_FIELD_TEXTENCODING_ISO_8859_1);
            is_utf16 = (id3_field_gettextencoding(field) == ID3_FIELD_TEXTENCODING_UTF_16);
            break;
        }
    }

    for (i = 0; (field = id3_frame_field(frame, i)); i++)
    {
        tmpstr = tmpstr2 = NULL;
        switch (field_type = id3_field_type(field))
        {
            case ID3_FIELD_TYPE_LATIN1:
            case ID3_FIELD_TYPE_LATIN1FULL:
                if (field_type == ID3_FIELD_TYPE_LATIN1)
                {
                    if (!(etag_field_type & EASYTAG_ID3_FIELD_LATIN1))
                        continue;
                }else
                    if (!(etag_field_type & EASYTAG_ID3_FIELD_LATIN1FULL))
                        continue;
                latinstr = g_strdup(field_type == ID3_FIELD_TYPE_LATIN1 ? (gchar *)id3_field_getlatin1(field) : (gchar *)id3_field_getfulllatin1(field));
                if (USE_NON_STANDARD_ID3_READING_CHARACTER_SET)
                {
                    tmpstr = convert_string(latinstr, FILE_READING_ID3V1V2_CHARACTER_SET, "UTF-8", FALSE);
                    free(latinstr);
                }
                else
                    tmpstr = latinstr;
                break;

            case ID3_FIELD_TYPE_STRING:
            case ID3_FIELD_TYPE_STRINGFULL:
                if (field_type == ID3_FIELD_TYPE_STRING)
                {
                    if (!(etag_field_type & EASYTAG_ID3_FIELD_STRING))
                        continue;
                }else
                    if (!(etag_field_type & EASYTAG_ID3_FIELD_STRINGFULL))
                        continue;
                usrc = (field_type == ID3_FIELD_TYPE_STRING) ? id3_field_getstring(field) : id3_field_getfullstring(field);
                retval |= etag_ucs42gchar(usrc, is_latin, is_utf16, &tmpstr);
                break;

            case ID3_FIELD_TYPE_STRINGLIST:
                if (!(etag_field_type & EASYTAG_ID3_FIELD_STRINGLIST))
                    continue;
                strcnt = id3_field_getnstrings(field);
                for (j = 0; j < strcnt; j++)
                {
                    retval |= etag_ucs42gchar(
                        id3_field_getstrings(field, j),
                        is_latin, is_utf16, &tmpstr );

                    if (tmpstr2 && *tmpstr2 && g_utf8_validate(tmpstr2, -1, NULL))
                    {
                        if (tmpstr)
                            tmpstr = g_strconcat(tmpstr, " ", tmpstr2, NULL);
                        else
                            tmpstr = g_strdup(tmpstr2);
                    }

                    free(tmpstr2);
                }

            default:
                break;
        }
        if (tmpstr && *tmpstr && g_utf8_validate(tmpstr, -1, NULL))
        {
            if (ret)
                ret = g_strconcat(ret, MULTIFIELD_SEPARATOR, tmpstr, NULL);
            else
                ret = g_strdup(tmpstr);
        }
        g_free(tmpstr);
    }

    if (retstr)
        *retstr = ret;
    else
        free(ret);

    return retval;
}

static int
etag_guess_byteorder(const id3_ucs4_t *ustr, gchar **ret) /* XXX */
{
    unsigned i, len;
    gunichar *gstr;
    gchar *tmp, *str, *str2;
    const gchar *charset;

    if (!ustr || !*ustr)
    {
        if (ret)
            *ret = NULL;
        return 0;
    }
    
    if (USE_NON_STANDARD_ID3_READING_CHARACTER_SET)
        charset = FILE_READING_ID3V1V2_CHARACTER_SET;
    else if (!FILE_WRITING_ID3V2_USE_UNICODE_CHARACTER_SET) /* XXX */
        charset = FILE_WRITING_ID3V2_NO_UNICODE_CHARACTER_SET;
    else g_get_charset(&charset);

    if (!charset)
        charset = "ISO-8859-1";

    tmp = (gchar *)id3_ucs4_utf8duplicate(ustr);
    str = g_convert(tmp, -1, charset, "UTF-8", NULL, NULL, NULL);
    if (str)
    {
        g_free(str);
        if (ret)
            *ret = tmp;
        else
            free (tmp);
        return 0; /* byteorder not changed */
    }

    for (len = 0; ustr[len]; len++);

    gstr = (gunichar*)g_try_malloc(sizeof(gunichar) * (len + 1));
    if ( gstr == NULL )
    {
        if (ret)
            *ret = tmp;
        else
            free(tmp);
        return 0;
    }

    for (i = 0; i <= len; i++)
        gstr[i] = ((ustr[i] & 0xff00) >> 8) | ((ustr[i] & 0xff) << 8);
    str = g_ucs4_to_utf8(gstr, len, NULL, NULL, NULL);
    g_free(gstr);

    if (str == NULL)
    {
        if (ret)
            *ret = tmp;
        else
            free(tmp);
        return 0;
    }

    str2 = g_convert(str, -1, charset, "UTF-8", NULL, NULL, NULL);

    if (str2 && *str2)
    {
        g_free(str2);
        free(tmp);
        if (ret)
            *ret = str;
        else
            free(str);
        return 1;
    }

    g_free(str);

    if (ret)
        *ret = tmp;
    else
        free(tmp);

    return 0;
}

static int
etag_ucs42gchar(const id3_ucs4_t *usrc, unsigned is_latin,
                unsigned is_utf16, gchar **res)
{
    gchar* latinstr;
    gchar* retstr;
    int retval;

    if (!usrc || !*usrc)
    {
        if (res)
            *res = NULL;
        return 0;
    }

    retval = 0, retstr = NULL;

    if (is_latin && USE_NON_STANDARD_ID3_READING_CHARACTER_SET)
    {
        if ((latinstr = (gchar *)id3_ucs4_latin1duplicate(usrc)))
        {
            retstr = (gchar*)""; //retstr = convert_string(latinstr, FILE_READING_ID3V1V2_CHARACTER_SET, "UTF-8", FALSE);
            free(latinstr);
        }
    }else
    {
        if (is_utf16)
        {
            retval |= etag_guess_byteorder(usrc, &retstr);
        }else
        {
            retstr = (gchar *)id3_ucs4_utf8duplicate(usrc);
        }
    }

    if (res)
        *res = retstr;
    else
        free (retstr);

    return retval;
}

